/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.


See LICENSE and AUTHORS for more information.
*/

#include <QtGui>
#include "buddyitem.h"
#include "buddymodel.h"

/**
* @brief constructor: setup column headers and a root item
* @param parent any QObject can be parent to this Model class
*/
BuddyModel::BuddyModel(QObject *parent)
    : QAbstractItemModel(parent)
{
    QVector<QVariant> rootData;
    rootData << "Name" << "Icon" << "JID" << "AccID";

    rootItem = new BuddyItem(rootData);
}

/**
* @brief destructor
*/
BuddyModel::~BuddyModel()
{
    delete rootItem;
}

/**
* @brief how many columns of data are there (for the rootItem)?
* @return the count of columns of data (of the root item)
* @param parent NOT USED. This is for compatibility only
*/
int BuddyModel::columnCount(const QModelIndex & /* parent */) const
{
    return rootItem->columnCount();
}

/**
* @brief get the Data that index points to, formatted by role
* @returns data pointed to by #index formatted to #role.
*
* If #index or #role are invalid, an empty QVariant() is returned.
* @param index QModelIndex pointing to the item and column which should be returned
* @param role the role that this data should be formatted for
*/
QVariant BuddyModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role != Qt::DisplayRole && role != Qt::EditRole)
        return QVariant();

    BuddyItem *item = getItem(index);

    return item->data(index.column());
}

/**
* @brief get the ItemFlags for an item in the model.
* @return Qt::ItemFlags of the item pointed to by index
*
* TODO: This is not depended on #index so far, other
* than that 0 is returned if it's invalid.
* We should at least remove Qt::ItemIsEditable from several
* columns such as accid or jid
* @param index QModelIndex whose flags should be returned
*/
Qt::ItemFlags BuddyModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;

    return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

/**
* @brief get the Item pointed to by index
* @return the BuddyItem pointed to by index
*
* The root item is returned for an invalid index.
* @param index QModelIndex pointing to an Item in the model
*/
BuddyItem *BuddyModel::getItem(const QModelIndex &index) const
{
    if (index.isValid()) {
        BuddyItem *item = static_cast<BuddyItem*>(index.internalPointer());
        if (item) return item;
    }
    return rootItem;
}

/**
* @brief get the column header for a section
* @return the column headers for section via the root item
*
* This wraps around BuddyItem::data() as the header data is stored in the rootItems
* data columns in this model.
*
* This function is provided for compatibility with the Qt Model/View implementations
* @param section the column index for which the header should be returned
* @param orientation whether to format Horizontal or Vertical, only the first is (yet) supported
* @param role What role is the data requested for. Only Qt::DisplayRole is supported, yet.
*/
QVariant BuddyModel::headerData(int section, Qt::Orientation orientation,
                               int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        return rootItem->data(section);

    return QVariant();
}

/**
* @brief get the QModelIndex for a given combination of row and column
* @return the index of a child of #parent pointed to by #row,#column
*
* defaults to QModelIndex() (invalid index) on error
* @param row take the {row}th child of parent
* @param column return the {column}th column of data of the Item
* @param parent index to the parent item. If invalid, rootItem is used
*/
QModelIndex BuddyModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() != 0)
        return QModelIndex();
    
    BuddyItem *parentItem = getItem(parent);

    BuddyItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

/**
* @brief insert columns into the data of an item pointed to by #parent
* @return true if successfully inserted
*
* this delegates to BuddyItem::insertColumns(int,int)
* @param position the column after which to insert the new columns
* @param columns how many columns to insert
* @param parent the parent as used in beginInsertColumns(..)
* @return true on successfull insert
*/
bool BuddyModel::insertColumns(int position, int columns, const QModelIndex &parent)
{
    bool success;

    beginInsertColumns(parent, position, position + columns - 1);
    success = rootItem->insertColumns(position, columns);
    endInsertColumns();

    return success;
}

/**
* @brief Inserts $rows rows as children to $parent, starting after the $position row
*
* delegates to BuddyItem::insertChildren(int,int,int)
* @param position start to insert after this child index
* @param rows how many rows to insert
* @param parent new rows are created as childs to this parent
* @return true on successfull insert
*/
bool BuddyModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    BuddyItem *parentItem = getItem(parent);
    bool success;

    beginInsertRows(parent, position, position + rows - 1);
    success = parentItem->insertChildren(position, rows, rootItem->columnCount());
    endInsertRows();

    return success;
}

/**
* @brief get the parent of an item by its index
* @return the parent of the given #index, QModelIndex() if it's invalid.
*
* @param index index pointing to an item in the model
*/
QModelIndex BuddyModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    BuddyItem *childItem = getItem(index);
    BuddyItem *parentItem = childItem->parent();

    if (parentItem == rootItem)
        return QModelIndex();

    return createIndex(parentItem->childNumber(), 0, parentItem);
}

/**
* @brief remove columns of data
*
* Remove $columns columns of data from the item referenced by parent, starting at
* the $position column.
*
* @return true on success
* @param position which is the first column to delete?
* @param columns how many columns to delete?
* @param parent delete from which item?
*/
bool BuddyModel::removeColumns(int position, int columns, const QModelIndex &parent)
{
    bool success;

    beginRemoveColumns(parent, position, position + columns - 1);
    success = rootItem->removeColumns(position, columns);
    endRemoveColumns();

    if (rootItem->columnCount() == 0)
        removeRows(0, rowCount());

    return success;
}

/**
* @brief removes $rows rows from $parent starting at $position.
* @return true on success
* @param position first row to delete
* @param rows how many rows to delete
* @param parent the item from which should be deleted
*/
bool BuddyModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    BuddyItem *parentItem = getItem(parent);
    bool success = true;

    beginRemoveRows(parent, position, position + rows - 1);
    success = parentItem->removeChildren(position, rows);
    endRemoveRows();

    return success;
}

/**
* @brief how many rows (children) does $parent have?
*
* @returns the count of rows of $parent
*
* delegates to BuddyItem::childCount()
*
* @param parent index of an item
*/
int BuddyModel::rowCount(const QModelIndex &parent) const
{
    BuddyItem *parentItem = getItem(parent);

    return parentItem->childCount();
}

/**
* @brief set the data of $index to $value
*
* $role must be Qt::EditRole
*
* @return true on success
* @param index the item and column whose data should be set
* @param value the data to be written
* @param role this MUST be Qt::EditRole in this model.
*/
bool BuddyModel::setData(const QModelIndex &index, const QVariant &value,
                        int role)
{
    if (role != Qt::EditRole)
        return false;

    BuddyItem *item = getItem(index);
    bool result = item->setData(index.column(), value);

    if (result)
        emit dataChanged(index, index);

    return result;
}

/**
* @brief update the header data of the model
*
* delegates to setting the rootItem's data
* @return true on success
* @param section which column to update
* @param orientation this MUST be Qt::Horizontal for this model
* @param value the value that the column header should be set to
* @param role this MUST be Qt::EditRole
*/
bool BuddyModel::setHeaderData(int section, Qt::Orientation orientation,
                              const QVariant &value, int role)
{
    if (role != Qt::EditRole || orientation != Qt::Horizontal)
        return false;

    bool result = rootItem->setData(section, value);

    if (result)
        emit headerDataChanged(orientation, section, section);

    return result;
}

/**
* @brief add a Group named $grp to the model
*
* All childs of the root item are considered groups.
* groups may have no parent other than the root item
* @param grp the group name
*/
void BuddyModel::addGroup(QString grp)
{
    rootItem->insertChildren(rootItem->childCount(),1,rootItem->columnCount());
    rootItem->child(rootItem->childCount() -1)->setData(0,grp);
    rootItem->child(rootItem->childCount() -1)->setData(1,"g");
}

/**
* @brief add a buddy to the Model as child of the Grp identified by $grp.
*
* Buddies must be childs of groups and may not be childs of the root Item
* nor childs of another buddy.
*
* @param grp the group name to fit in
* @param name the Buddy's name
* @param jid the Buddy's JID
* @param accID the account that this buddy is registered to
*/
void BuddyModel::addBuddy(QString grp,QString name,QString jid,int accID)
{
    for (int i=0;i<rootItem->childCount();i++)
    {
        if(rootItem->child(i)->data(0) == grp)
        {
            rootItem->child(i)->insertChildren(rootItem->child(i)->childCount(),1,rootItem->columnCount());
            rootItem->child(i)->child(rootItem->child(i)->childCount() -1)->setData(0,name);
            rootItem->child(i)->child(rootItem->child(i)->childCount() -1)->setData(1,"b");
            rootItem->child(i)->child(rootItem->child(i)->childCount() -1)->setData(2,jid);
            rootItem->child(i)->child(rootItem->child(i)->childCount() -1)->setData(3,accID);
        }
    }

}

/**
* @brief get a Buddy's name by its jid and accid
*
* return the Name of a buddy in the model, identified by its jid
* relative to an account-ID. returns the jid's local part if the buddy is not found.
* (e.g. the buddy is not on the roaster yet)
*
* @note Note: This method might search the whole model each time it is run,
* so do not use it too excessivly. Try to remember the Username where
* possible and register on the user changed Signals (-> TODO)
* @return the buddy's name
* @param accid the account id this buddy is associated with
* @param jid the jid of the buddy
*/
QString BuddyModel::getBuddyName(int accid,QString jid)
{
    for(int i=0;i<rootItem->childCount();i++)
    {
        BuddyItem *c = rootItem->child(i);
        for(int j=0;j<c->childCount();j++)
        {
            if(c->child(j)->data(2).toString() == jid && c->child(j)->data(3).toInt() == accid)
                return c->child(j)->data(0).toString();
        }
    }
    // return the localpart of the JID if the buddy is not within the model.
    return  jid.split("@").value(0);
}
